package com.ustc.hewei.nettyrpc2_0.netty.client;

import com.ustc.hewei.nettyrpc2_0.common.config.PropertyFactory;
import com.ustc.hewei.nettyrpc2_0.common.config.RpcProperties;
import com.ustc.hewei.nettyrpc2_0.common.enumeration.RpcError;
import com.ustc.hewei.nettyrpc2_0.common.exception.InvokeMethodException;
import com.ustc.hewei.nettyrpc2_0.common.exception.RpcException;
import com.ustc.hewei.nettyrpc2_0.common.mock.MockInterface;
import com.ustc.hewei.nettyrpc2_0.common.mock.MultiMock;
import com.ustc.hewei.nettyrpc2_0.common.utils.Constants;
import com.ustc.hewei.nettyrpc2_0.common.utils.PropertiesUtil;
import com.ustc.hewei.nettyrpc2_0.common.warp.RpcRequest;
import com.ustc.hewei.nettyrpc2_0.common.warp.RpcResponse;
import com.ustc.hewei.nettyrpc2_0.netty.client.handler.ClientHandler;
import com.ustc.hewei.nettyrpc2_0.netty.client.manager.HandlerManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* @description: 为消费者建立代理对象
* @author hewei
* @date 2022/9/13 19:59
* @version 1.0
*/

@Slf4j
public class ClientProxy implements InvocationHandler {
    private final HandlerManager handlerManager;
    private final MultiMock multiMock;

    public ClientProxy(HandlerManager handlerManager, MultiMock multiMock) {
        this.handlerManager = handlerManager;
        this.multiMock = multiMock;
    }

    public <T> T getProxy(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long retried = PropertiesUtil.getLong(Constants.retried);
        // 判断方法是不是从Object继承过来的方法
        if (Object.class == method.getDeclaringClass()) {
            String name = method.getName();
            switch (name) {
                case "equals":
                    return proxy == args[0];
                case "hashCode":
                    return System.identityHashCode(proxy);
                case "toString":
                    return proxy.getClass().getName() + "@" +
                            Integer.toHexString(System.identityHashCode(proxy)) +
                            ", with InvocationHandler " + this;
                default:
                    throw new IllegalStateException(String.valueOf(method));
            }
        }

        long timeout = PropertiesUtil.getLong(Constants.waitResponseTimeout);
        // 获得最外层的接口名，因为有时候使用method.getDeclaringClass().getName()获取到的是IService接口
        Class<?>[] interfaces = proxy.getClass().getInterfaces();
        String interfaceName = interfaces[0].getName();
        ClientHandler handler = null;
        RpcRequest request = new RpcRequest().setHeartBeat(false).setId(UUID.randomUUID().toString())
                .setInterfaceName(interfaceName).setMethodName(method.getName())
                .setParameters(args).setParamTypes(method.getParameterTypes());
        RpcResponse response;
        while (retried-- > 0) {
            try {
                handler = handlerManager.chooseHandler(interfaceName);
                if (handler == null) {
                    break;
                }
                if (handler.getChannel().isActive()) {
                    CompletableFuture<RpcResponse> future = handler.sendRequest(request);
                    response = future.get(timeout, TimeUnit.MILLISECONDS);
                    if (!response.getIsSuccess()) {
                        throw new InvokeMethodException(response.getException());
                    }
                    return response.getData();
                } else {
                    throw new RpcException(RpcError.INACTIVE_HANDLER, String.format("服务端 {%S} handler失效，准备重新连接", handler.getRemoteAddress()));
                }
            } catch (InterruptedException | ExecutionException | TimeoutException | RpcException | InvokeMethodException e) {
                // 表面上是统一降级，但是如果是服务端的错误，则会直接从zk删除，没有活过来的可能，
                // 但是超时异常只要连接不断，过段时间就会活过来
                if (e instanceof TimeoutException) {
                    log.error("handler超时异常, 服务降级!");
                    handler = handlerManager.retried(handler, interfaceName);
                } else if (e instanceof InvokeMethodException || e instanceof RpcException) {
                    log.error("服务端调用异常: {}", e.getMessage());
                    handler = handlerManager.retried(handler, interfaceName);
                } else {
                    throw new RuntimeException(e);
                }
            }
        }
        log.error("调用服务超过指定次数或是无目标服务 {}", interfaceName);
        // TODO: 2022/10/28 不同的服务是否可以设置不同的mock
        MockInterface mock = multiMock.getMock(interfaceName);
        if (mock == null) {
            return null;
        } else {
            return mock.defaultProcess(args);
        }
    }
}